


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include "door.h"
#include "catdoors.h"
#include "catpeople.h"
#include "expression.h"
#include "mutator.h"



using namespace std;



/// \return true - permutations are all proved wrong, false - one of permutations is matching
bool checkAllPermutations( CollectionSubjects &doors, CollectionCategories &catdoors, CollectionSubjects &people, CollectionCategories &catpeople )
{
    GlobalMutator mutator( &doors, &catdoors, &people, &catpeople );
    
    while( !mutator.mutate() )
    {            
        // make a check for all the expressions
        if( mutator.check() )
            return false;
    } // while( true )

    return true;
}



/// deletes unused categories from the collection
void cleanCategories( CollectionCategories &categories )
{
    // clean the categories from unused ones
    CollectionCategories::iterator ic = categories.begin(), ic_end = categories.end();
    while( ic != ic_end )
    {
        Category *cat = *ic;
        if( cat->getRefCount() == 0 )
        {
            ic = categories.erase( ic );
            delete cat;
        }
        else
            ++ic;
    }
}



bool removeRandomExpressions( CollectionSubjects &doors, CollectionCategories &doorcats, CollectionSubjects &people, CollectionCategories &peoplecats )
{
    uint16_t removed = 0;
    
    for( uint8_t unsuccessfull_attempts = 0; unsuccessfull_attempts < 5; )
    {                
        // get the random door with nonempty expressions
        Subject *subj = doors.getRandom();
        CollectionExpressions *exprs = &subj->getExpressions();
        if( exprs->size() <= 1 )
        {
            ++unsuccessfull_attempts;
        }
        else
        {
            // get and remove the random expression from the collection
            Expression *exp = exprs->getRandom();
            exprs->remove(exp);
        
            if( checkAllPermutations(doors, doorcats, people, peoplecats) )
            {
                // other expression is still false - we can delete the removed one
                ++removed;
                delete exp;
                cleanCategories( doorcats );
                unsuccessfull_attempts = 0;
            }
            else
            {
                // check ok, restoring the expression
                exprs->push_back( exp );
                ++unsuccessfull_attempts;
            }
        }
            
        if( people.empty() )
            continue;
            
        // get the random person with nonempty expressions
        subj = people.getRandom();                
        exprs = &subj->getExpressions();
        if( exprs->size() <= 1 )
        {
            ++unsuccessfull_attempts;
        }
        else
        {
            // get and remove the random expression from the collection
            Expression *exp = exprs->getRandom();
            exprs->remove(exp);
        
            if( checkAllPermutations(doors, doorcats, people, peoplecats) )
            {
                // other expression is still false - we can delete the removed one
                ++removed;
                delete exp;
                cleanCategories( doorcats );
                cleanCategories( peoplecats );
                unsuccessfull_attempts = 0;
            }
            else
            {
                // check ok, restoring the expression
                exprs->push_back( exp );
                ++unsuccessfull_attempts;
            }
        }
    } // for( uint8 unsuccessfull_attempts = 0; unsuccessfull_attempts < 5; )
    
    return removed > 0;
}



int main( int argc, char **argv )
{
    CollectionSubjects doors;
    CollectionSubjects people;
    CollectionCategories doorcats;
    CollectionCategories peoplecats;
    
    // first argument is a rnd() seed
    if( argc > 1 )
    {
        int seed;
        if( 1 == sscanf( argv[1], "%d", &seed ) )
            srand( seed );
    }
    
    // second argument is a level level
    uint8_t level = 0;
    if( argc > 2 )
    {
        int compx;
        if( 1 == sscanf( argv[2], "%d", &compx ) && compx < 100 )
            level = uint8_t(compx);
        else
            level = 100;
    }
    
    int nSubjects = level/20 + 2;
    int nDoors = rand()%nSubjects;
    if( nDoors < 2 ) nDoors = 2; else if( nDoors > 5 ) nDoors = 5;
    
    int nPeople = nSubjects - nDoors;
    if( nPeople < 0 ) nPeople = 0; else if( nPeople > 3 ) nPeople = 3;
    
    Color::eColor MAX_DOOR_COLOR = Color::eColor( nDoors > 3 ? 3 : nDoors );
    Color::eColor MAX_PERSON_COLOR = Color::eColor( nPeople > 3 ? 3 : nPeople );
    
// create some doors
    bool all_traps = true;
    for( int i = 1; i <= nDoors; ++i )
    {
        Door *door = new Door( i,
                               rand()%240 - 120 > int(level),
                               rand()%2 != 0,
                               Color::eColor( rand() % ( int(MAX_DOOR_COLOR) ) ) );

        if( door->isSafe() )
            all_traps = false;
        
        doors.push_back( door );        
    }
    
    // if all doors are traps - make one safe
    if( all_traps )
    {
        Door *door = static_cast <Door*> (doors.getRandom());
        door->makeSafe();
    }

// create some people
    for( int i = 1; i <= nPeople; ++i )
    {
        Person *person = new Person( Person::getRandomName(),
                                     PersonType( PersonType::eType(rand()%3) ),
                                     rand()%(level + 1) > 10,      
                                     rand()%2 != 0,
                                     rand()%2 != 0,
                                     Color::eColor( rand() % ( int(MAX_PERSON_COLOR) ) ) );
        
        people.push_back( person );        
    }

// the third argument of the application selects between checking and generating
    if( argc > 3 && !strcmp( argv[3], "check" ) )
    {
        // output the doors
        CollectionSubjects::iterator id = doors.begin(), id_end = doors.end();
        for( ; id != id_end; ++id )
            cout << **id;
        
        if( !people.empty() )
        {
            // separator between the doors texts and the people texts
            cout << "&\n";

            // output the people
            id = people.begin(), id_end = people.end();
            for( ; id != id_end; ++id )
                cout << **id;
        }
        
        exit(0);
    }
    
// decide, will there be fallen doorsigns
    if( level && rand()%level > 50 )
    {
        for( uint8_t fctr = 2; fctr; )
        {
            Door *door = static_cast <Door*> (doors.getRandom());
            if( door->hasSign() )
            {
                --fctr;
                door->setSign( false );
            }
        }
    }
        
// create door categories - doorcats
    for( CollectionSubjects::iterator id = doors.begin(); id != doors.end(); ++id )
        doorcats.push_back( CatDoor::createCategory( *id ) );
    
    Category *cat;
    for( int i = 0; i < MAX_DOOR_COLOR; ++i )
    {
        cat = CatColorDoors::createCategory( doors, Color::eColor(i), Color::eColor(MAX_DOOR_COLOR) );
        if( cat )
            doorcats.push_back( cat );
    }
    
    cat = CatEvenDoors::createCategory( doors );
    if( cat )
        doorcats.push_back( cat );
    
    cat = CatOddDoors::createCategory( doors );
    if( cat )
        doorcats.push_back( cat );
    
    cat = CatAllDoors::createCategory( doors );
    if( cat )
        doorcats.push_back( cat );
    
    cat = CatTrapDoors::createCategory( doors );
    if( cat )
        doorcats.push_back( cat );

    cat = CatSafeDoors::createCategory( doors );
    if( cat )
        doorcats.push_back( cat );
    
    cat = CatTruthDoors::createCategory( doors );
    if( cat )
        doorcats.push_back( cat );

    cat = CatLyingDoors::createCategory( doors );
    if( cat )
        doorcats.push_back( cat );

// generate expressions about doors, without the "this door" category, give them to the people
    if( !people.empty() )
    {
        for( CollectionCategories::iterator ic = doorcats.begin(); ic != doorcats.end(); ++ic )
        {
            CollectionSubjects::iterator ip = people.begin(), ip_end = people.end();
            for( ; ip != ip_end; ++ip )
                (*ic)->generate( doors, *ip, level);
        }
    }

// now add the "this door" category    
    doorcats.push_back( CatThisDoor::createCategory() );
    
// generate expressions about doors, give them to doors
    for( CollectionCategories::iterator ic = doorcats.begin(); ic != doorcats.end(); ++ic )
    {
        CollectionSubjects::iterator id = doors.begin(), id_end = doors.end();
        for( ; id != id_end; ++id )
            (*ic)->generate( doors, *id, level);
    }

// create people categories - peoplecats
    if( !people.empty() )
    {
        
        for( CollectionSubjects::iterator ip = people.begin(); ip != people.end(); ++ip )
            peoplecats.push_back( CatName::createCategory( people, *ip ) );
        
        peoplecats.push_back( CatThisPerson::createCategory() );
        
        cat = CatNeighbours::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );
    
        for( int i = 0; i < MAX_PERSON_COLOR; ++i )
        {
            cat = CatColorPeople::createCategory( people, Color::eColor(i), Color::eColor(MAX_PERSON_COLOR) );
            if( cat )
                peoplecats.push_back( cat );
        }
        
        cat = CatAllPeople::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatTheLeftPerson::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatTheRightPerson::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatMiddlePerson::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatFatPeople::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatSlimPeople::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );

        cat = CatIncognitoPeople::createCategory(people);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatType::createCategory(people, PersonType::HUMAN);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatType::createCategory(people, PersonType::WIZARD);
        if( cat )
            peoplecats.push_back( cat );

        cat = CatType::createCategory(people, PersonType::VAMPIRE);
        if( cat )
            peoplecats.push_back( cat );
        
        cat = CatHonestPeople::createCategory( people );
        if( cat )
            peoplecats.push_back( cat );

        cat = CatLyingPeople::createCategory( people );
        if( cat )
            peoplecats.push_back( cat );
    }
    
// generate expressions about people, give them to people
    for( CollectionCategories::iterator ic = peoplecats.begin(); ic != peoplecats.end(); ++ic )
    {
        CollectionSubjects::iterator ip = people.begin(), ip_end = people.end();
        for( ; ip != ip_end; ++ip )
            (*ic)->generate( people, *ip, level);
    }
    
// formal checking
/*
    GlobalMutator mutator(&doors, &doorcats, &people, &peoplecats);
    if( mutator.check() != true )
    {
        cout << "initial check failed";
        exit(0);
    }
*/  
// remove random expressions
    if( !removeRandomExpressions(doors, doorcats, people, peoplecats) )
    {        
        // put back signs on all doors
        CollectionSubjects::iterator id = doors.begin(), id_end = doors.end();
        for( ; id != id_end; ++id )
        {
            Door *door = static_cast <Door*> (*id);
            door->setSign(true);
        }

        removeRandomExpressions(doors, doorcats, people, peoplecats);
    }
        
    if( !people.empty() )
    {
// remove all the rest of expressions, first from people
        CollectionSubjects subs_copy = people;
        while( subs_copy.begin() != subs_copy.end() )
        {                
            // get the random door with nonempty expressions
            Subject *subj = subs_copy.getRandom();
            CollectionExpressions *exprs = &subj->getExpressions();

            CollectionExpressions::iterator ie = exprs->begin(), ie_end = exprs->end();
            for(  ; exprs->size() > 1 && ie != ie_end; )
            {
                Expression *exp = *ie;
                ++ie;

                // remove the expression from the collection
                exprs->remove(exp);
        
                if( checkAllPermutations(doors, doorcats, people, peoplecats) )
                {
                    // other expression is still false - we can delete the removed one
                    delete exp;
                    cleanCategories( peoplecats );
                }
                else
                {
                    // check ok, restoring the expression
                    exprs->push_front( exp );
                }
            }
            subs_copy.remove(subj);
        } // while( subs_copy.begin() != subs_copy.end() )
    } // if( !people.empty() )

// remove all the rest of expressions, from doors
    CollectionSubjects subs_copy = doors;
    while( subs_copy.begin() != subs_copy.end() )
    {                
        // get the random door with nonempty expressions
        Subject *subj = subs_copy.getRandom();
        CollectionExpressions *exprs = &subj->getExpressions();

        CollectionExpressions::iterator ie = exprs->begin(), ie_end = exprs->end();
        for( ; exprs->size() > 1 && ie != ie_end; )
        {
            Expression *exp = *ie;
            ++ie;

            // remove the expression from the collection
            exprs->remove(exp);
        
            if( checkAllPermutations(doors, doorcats, people, peoplecats) )
            {
                // other expression is still false - we can delete the removed one
                delete exp;
                cleanCategories( doorcats );
            }
            else
            {
                // check ok, restoring the expression
                exprs->push_front( exp );
            }
        }
        subs_copy.remove(subj);                    
    } // while( subs_copy.begin() != subs_copy.end() )
    
// print this all
    for( CollectionSubjects::iterator id = doors.begin(); id != doors.end(); ++id )
    {
        cout << **id;

        // writing the expressions uninverted
        CollectionExpressions::iterator ie = (*id)->getExpressions().begin(), ie_end = (*id)->getExpressions().end();
        for( ; ie != ie_end; ++ie )
        {
            (*ie)->putText( cout, *id, false ); // display non-inverted expression
            cout << "Inverted: ";
            (*ie)->putText( cout, *id, true );  // display inverted expression
        }
    }
    
    if( !people.empty() )
    {
        // separator between the doors texts and the people texts
        cout << "&\n";
    
        for( CollectionSubjects::iterator ip = people.begin(); ip != people.end(); ++ip )
        {
            cout << **ip;

            // writing the expressions uninverted
            CollectionExpressions::iterator ie = (*ip)->getExpressions().begin(), ie_end = (*ip)->getExpressions().end();
            for( ; ie != ie_end; ++ie )
            {
                (*ie)->putText( cout, *ip, false ); // display non-inverted expression
                cout << "Inverted: ";
                (*ie)->putText( cout, *ip, true );  // display inverted expression
            }
        }
    }

    exit(0);
}
